package model;

import java.util.Observable;
import java.util.Random;

public class Celula extends Observable {

	// static volatile boolean com_cobra = false;

	private volatile boolean reservada = false;

	private volatile boolean ocupada = false;

	/**
	 * @return Getter da reserva da celula.
	 * 
	 */
	public boolean isReservada() {
		return reservada;
	}

	/**
	 * @param com_cobra
	 */
	public void setReservada(boolean reservar) {
		this.reservada = reservar;
	}

	private final int celula_id;

	/**
	 * @return Getter do id da célula (inteiro maior ou igual a 0 e menor do que
	 *         model.World.lado*model.World.lado
	 */
	public int getCelula_id() {
		return celula_id;
	}

	private volatile int cobra_id = -1;

	/**
	 * @return Getter do id da cobra; -1 para cobra inexistente (usado para
	 *         desenho das celulas).
	 */
	public int getCobra_id() {
		return cobra_id;
	}

	/**
	 * @param cobra_id
	 *            Setter do id da cobra, usado quando a mesma está a ser ocupada
	 *            por uma cobra
	 */
	public void setCobra_id(int cobra_id) {
		this.cobra_id = cobra_id;
	}

	private boolean e_cabeca = false;

	private final int dificuldade;

	/**
	 * @return Getter da cabeça, devolve o boleano que representa se a celula
	 *         tem a cabeça de alguma cobra.
	 */
	public boolean isE_cabeca() {
		return e_cabeca;
	}

	/**
	 * @param setter
	 *            da cabeça da cobra.
	 * 
	 */
	public void setE_cabeca(boolean e_cabeca) {
		this.e_cabeca = e_cabeca;
	}

	/**
	 * Popula celulas aleatoriamente com blocos, comida, turbo, salto ou nada. o
	 * parametro dificuldade deverá ser entre superior a 1; Quanto maior o valor
	 * da dificuldade (passada pelo construtor), menos denso será o tabuleiro.
	 */
	public void populacelula() {
		Random rand = new Random();

		// !!! aumentar o numero diminui a complexidade do mundo
		switch (rand.nextInt(21 * dificuldade)) {
		case 0:
			setBlock();
			break;
		case 1:
			setFood();
			break;
		case 2:
			setTurbo();
			break;
		case 3:
			setJump();
			break;
		case 4:
			setFood();
			break;
		case 5:
			setFood();
			break;
		case 6:
			setFood();
			break;
		case 7:
			setBlock();
			break;
		}
		;

		setChanged();
		notifyObservers();

	}

	/**
	 * @param cobra
	 *            - id da cobra que passa a ocupar esta celula;
	 * @param e_cabeca
	 *            - booleano que indica se a celula contém a cabeça de uma cobra
	 *            assim que uma celula e ocupada por uma cobra, e chamado um
	 *            update ao observador.
	 */
	public synchronized void ocupaCelula(int cobra, boolean e_cabeca) {

		this.reservada = true;
		this.cobra_id = cobra;
		this.e_cabeca = e_cabeca;
		this.ocupada = true;
		setChanged();
		notifyObservers();

	}

	/**
	 * deixa a celula vazia de quaisquer estados relacionados com cobras. Inclui
	 * notificacao a threads que estejam em wait a este objecto
	 */
	public synchronized void desocupaCelula() {
		this.reservada = false;
		this.cobra_id = -1;
		this.e_cabeca = false;
		this.ocupada = false;
		this.notify();

	}

	/**
	 * @param possivel_cobra
	 *            metodo de reserva do recurso partilhado. Utilizado wait-notify
	 *            de modo a parar cobras que tentem aceder a este objecto. A
	 *            condição verificada é o booleano de reserva de celula
	 */
	public synchronized void reservaCelula(int possivel_cobra) {
		// System.out.println(com_cobra + "a reservar");
		while (reservada) {

			try {
				System.out.println(possivel_cobra + " a dormir!");
				wait();
				System.out.println(possivel_cobra + " Acordou!");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		this.reservada = true;
		notify();
		// System.out.println(cobra + " colocada!");

	}

	public enum Conteudo {
		NONE, FOOD, TURBO, BLOCK, JUMP
	};

	private Conteudo bonus = Conteudo.NONE;

	/**
	 * @param id
	 * @param dificuldade
	 *            a dificuldade é um inteiro - quanto mais pequeno, mais
	 *            difícil. Não pode ter valores inferiores a 1 . A dificudlade
	 *            representa a densidade de obstaculos e brindes presentes em
	 *            jogo.
	 */
	public Celula(int id, int dificuldade) {
		this.celula_id = id;
		this.dificuldade = dificuldade;
		populacelula();
	}

	/**
	 * Define esta celula como um bloco. Blocos nunca são alvo de tentativa de
	 * invasão de celula
	 */
	protected void setBlock() {
		bonus = Conteudo.BLOCK;
		reservada = true;

	}

	/**
	 * @return
	 */
	protected boolean isBlock() {
		return bonus == Conteudo.BLOCK;
	}

	/**
	 * define uma celula como vazia. Usado para esgotar os bonus
	 */
	protected void setNone() {
		bonus = Conteudo.NONE;
		// sem_cobra = false;

	}

	/**
	 * @return verdadeiro se a celula está vazia , nao contendo obstaculos,
	 *         brindes ou cobras.
	 */
	protected boolean isNone() {
		return bonus == Conteudo.NONE;
	}

	/**
	 * Define uma celula como contendo comida.
	 */
	protected void setFood() {
		bonus = Conteudo.FOOD;
		// sem_cobra = false;
	}

	/**
	 * @return retorna verdadeiro se a celula tem comida.
	 */
	protected boolean isFood() {
		return bonus == Conteudo.FOOD;

	}

	/**
	 * Define uma celula como contendo turbo
	 */
	protected void setTurbo() {
		bonus = Conteudo.TURBO;
		// sem_cobra = false;
	}

	/**
	 * @return verdadeiro se uma celula contém turbo
	 */

	protected boolean isTurbo() {
		return bonus == Conteudo.TURBO;

	}

	/**
	 * Define uma celula como salto ao eixo
	 */
	protected void setJump() {
		bonus = Conteudo.JUMP;
		// sem_cobra = false;
	}

	/**
	 * @return verdadeiro se uma célula contém o salto ao eixo.
	 */
	protected boolean isJump() {
		return bonus == Conteudo.JUMP;

	}

	/**
	 * @return Devolve o enumerado em si referente ao tipo de bonus da célula
	 *         (torna-se mais simples de aceder para condições switch)
	 */
	public Conteudo getBonus() {
		return bonus;

	}

}
